﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Data;
using System.Linq;
using System.Text;
using System.ServiceProcess;
using System.Threading.Tasks;
using System.Timers;
using System.Data.Linq;
using KC.Business.Meta;
using KC.DataAccess.MV6;
using KC.Business.Meta.Jobs;

namespace KC.Presentation
{
	/// <summary>META - Managed Ethereal Transaction Analyzer</summary>
	public partial class Meta : ServiceBase
	{
		private Timer tDaily = new Timer(86400000);
		private Timer tHourly = new Timer(3600000);
		private Timer tQueueRunner = new Timer(30000);
		private List<JobBase> Queue = new List<JobBase>();
		private bool isRunning = false;
		

		public Meta()
		{
			InitializeComponent();
			tDaily.Elapsed += new ElapsedEventHandler(tDaily_Elapsed);
			tHourly.Elapsed += new ElapsedEventHandler(tHourly_Elapsed);
			tQueueRunner.Elapsed += new ElapsedEventHandler(tQueueRunner_Elapsed);
		}

		protected override void OnStart(string[] args)
		{
			tDaily.Enabled = true;
			tDaily.Start();

			tHourly.Enabled = true;
			tHourly.Start();

			tQueueRunner.Enabled = true;
			tQueueRunner.Start();

			System.Threading.Thread.Sleep(250);
		}
		protected override void OnStop()
		{
			tDaily.Dispose();
		}
		protected void tDaily_Elapsed(object sender, ElapsedEventArgs e)
		{
			Queue.Add(new BlogCleanupJob());
			Queue.Add(new HeuristicsJob());
			Queue.Add(new SearchStatusUpdateJob());

			// Do not run Daily Report here. It will run after the queue clears and all other Daily jobs are complete.
		}
		protected void tHourly_Elapsed(object sender, ElapsedEventArgs e)
		{

		}
		/// <summary>Processes the current job queue</summary>
		protected void tQueueRunner_Elapsed(object sender, ElapsedEventArgs e)
		{
			if (isRunning)
				return;
				
			// Job preparation
			MV6DataContext dc = new MV6DataContext();
			Parallel.ForEach(Queue, job => {
				ServiceJob sj = BuildNewJob(job);
				dc.ServiceJobs.InsertOnSubmit(sj);
				dc.SubmitChanges();
				job.sj = sj; });

			// Job execution
			int numErrors = 0;
			Parallel.ForEach(Queue, job => {
				UpdateJobStatus(dc, job, JobStatus.Processing);
				try { job.Process(); }
				catch {
					UpdateJobStatus(dc, job, JobStatus.Error);
					numErrors++; }
				finally {
					if (job.sj.Status != JobStatus.Error.ToString())
						UpdateJobStatus(dc, job, JobStatus.Processed); }
				if (numErrors > 10) { return; } });

			// Job Disposal
			Parallel.ForEach(Queue, job => {
				UpdateJobStatus(dc, job, JobStatus.PostProcessing);
				job.PostProcess();
				UpdateJobStatus(dc, job, JobStatus.PostProcessed);
				Queue.Remove(job); });

			// Run daily report AFTER all other daily jobs are complete
			Queue.Add(new DailyReportJob());
			
		}
		/// <summary>Updates the status of a job.</summary>
		/// <param name="dc">MV6 Datacontext</param>
		/// <param name="job">Job to update</param>
		/// <param name="status">Status to set job to</param>
		private static void UpdateJobStatus(MV6DataContext dc, JobBase job, JobStatus status)
		{
			ServiceJob sj = (from s in dc.ServiceJobs where s.ID == job.sj.ID select s).First();
			sj.Status = status.ToString();
			if (status == JobStatus.Done)
				sj.dEnded = DateTime.Now;
			dc.SubmitChanges();
		}
		/// <summary>Adds a result to the result set of a ServiceJob</summary>
		/// <param name="dc">DataContext</param>
		/// <param name="sj">ServiceJob to add to</param>
		/// <param name="result">Result to append</param>
		public static void AssignResult(MV6DataContext dc, int sjID, string result)
		{
			if (dc == null)
				throw new ArgumentNullException("dc", "This throw exists just to satisfy CA1062.");
			ServiceJob sj = (from s in dc.ServiceJobs where s.ID == sjID select s).First();
			if (sj.Results == null) sj.Results = "";
			else sj.Results += ";";
			sj.Results += result;
			dc.SubmitChanges();
		}
		/// <summary>Creates a new ServiceJob based on a JobBase</summary>
		/// <param name="job">JobBase to base ServiceJob on</param>
		/// <returns>ServiceJob ready for insertion</returns>
		public static ServiceJob BuildNewJob(JobBase job)
		{
			if (job == null)
				throw new ArgumentNullException("job", "Job is null");
			ServiceJob sj = new ServiceJob();
			sj.args = job.args;
			return sj;
		}
	}
}
